#include "common.h"
#include "server.h"
#include <pthread.h>

/**
 * Main function of sub-thread, handling the request from each client side.
 * @param _c socket descriptor
 * @return null
 */
void* communication(void* _c) {
    chdir("/"); //change current working directory name to
    int connection = *(int*)_c , bytes_read;
    char buffer[BSIZE];
    Command *cmd = malloc(sizeof(Command)); // receive the client commands
    State *state = malloc(sizeof(State));   // store the necessary runtime information of TCP

    /* Init state information */
    state->username = NULL;
    state->connection = connection;
    state->userIP = malloc(USERIP_LENGTH);
    state->message = "220 A very warm welcome!\n";
    state->username = malloc(USERNAME_LENGTH);
    get_peer_ip_ascii(state->connection,state->userIP); //get ascii ip from socket descriptor

    /* Write welcome message */
    write_state(state);
    memset(buffer,0,BSIZE);
    /* Read commands from client, if client closed, then quit the while loop  */
    while ((bytes_read = read(state->connection,buffer,BSIZE) > 0)){
        /* Parse and Response */
        if(bytes_read<=BSIZE){

            /* Parsing command */
            parse_command(buffer,cmd);

            /* Print received information */
            printf("User %s, %s,sent command: %s %s\n",(state->username==NULL)?"unknown":state->username, state->userIP ,cmd->command, cmd->arg); //show client side information

            /* Ignore non-ascii char. Ignores telnet command */
            if(buffer[0]<=127 || buffer[0]>=0){
                response(cmd,state);
            }

            /* Clear */
            memset(buffer,0,BSIZE);
            memset(cmd,0,sizeof(cmd));
        }else{
            /* Read error */
            perror("server:read");
        }
    }

    /* Close the TCP connection and print the closed message */
    close(connection);
    printf("Client %s, %s disconnected.\n", (state->username ==NULL)?"unknown":state->username ,state->userIP);

    /* Free the malloced space */
    free(cmd);
    free_state(state);
    free(_c);

    return NULL;
}

/**
* Sets up server and handles incoming connections
* @param port Server port
*/
void server(int port)
{
    //prepare for the ftp working directory.
    if(mk_dir(SERVERROOTPATH)!=0){
        exit(0);
    }

    //set default ftp server root work path.You can customize the path.
   if(chroot(SERVERROOTPATH) !=0)
   {
       printf("Change directory to %s failed.\n",SERVERROOTPATH);
       exit(0);
   }
    // create the socket and bind it with port and listen
    int sock = create_socket(port);

    //keep listening and accept the new ones
    while(1){
        int *connection = (int*)malloc(sizeof(int));
        *connection = accept_connection(sock);

        pthread_t pid;
        pthread_create(&pid, NULL, communication, (void*) (connection));
    }

}

/**
* Creates socket on specified port and starts listening to this socket
* @param port Listen on this port
* @return int File descriptor for new socket
*/
int create_socket(int port)
{
    int sock;
    int reuse = 1;
    struct sockaddr_in server_address = (struct sockaddr_in){
            AF_INET,
            htons(port),    //the port number of this host
            (struct in_addr){INADDR_ANY}//0.0.0.0 ip address of this host
    };

    if((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0){
        fprintf(stderr, "Cannot open socket");//Formatted output to a stream file. stderr Output to the screen by default.
        exit(EXIT_FAILURE); //Represents an abnormal exit
    }

    /* Address can be reused instantly after program exits */
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse);

    /* Bind socket to server address */
    if(bind(sock,(struct sockaddr*) &server_address, sizeof(server_address)) < 0){
        fprintf(stderr, "Cannot bind socket to address");
        exit(EXIT_FAILURE);
    }

    listen(sock,5); //set the queue number
    return sock;
}

/**
*  set PORT socket
*  create new socket, not bind, not listen
*/
int set_socket(int port, State *state)
{
    int sock;
    int reuse = 1;
    struct sockaddr_in server_address = (struct sockaddr_in){
            AF_INET,
            htons(port),    //the port number of this host
            (struct in_addr){INADDR_ANY}//0.0.0.0 ip address of this host
    };

    if((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        fprintf(stderr, "Cannot open socket");//Formatted output to a stream file. stderr Output to the screen by default.
        exit(EXIT_FAILURE); //Represents an abnormal exit
    }

    /* Address can be reused instantly after program exits */
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse);

    /* Bind socket to server address */
    if(bind(sock,(struct sockaddr*) &server_address, sizeof(server_address)) < 0){
        fprintf(stderr, "Cannot bind socket to address");
        exit(EXIT_FAILURE);
    }

    return sock;
}

/**
 * @param state runtime state
 * @param sock client-side data socket
 * @return socket descriptor of sock_active
 */
int connect_clint_data_socket(State* state, int* sock){
    int ip[4];
    get_peer_ip(state->connection,ip);
    char ipv4[25];
    sprintf(ipv4, "%d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]);

//    printf("***connect_clint_data_socket mode:%d portdata:%d sockdata:%d connection:%d\n", state->mode, state->port_active,state->sock_active, state->connection);
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(state->port_active);
    client_addr.sin_addr.s_addr = inet_addr(ipv4);

    int x = connect(sock, &client_addr, sizeof(client_addr)); // PORT
//    printf("***connect_clint_data_socket9 mode:%d portdata:%d sockdata:%d connection:%d sock：%d connection:%d\n", state->mode, state->port_active,state->sock_active, state->connection, sock, x);
    if (x < 0)
        return x;
    return state->sock_active;
}

/**
* Accept connection from client
* @param socket Server listens this
* @return int File descriptor to accepted connection
*/
int accept_connection(int socket)
{
    int addrlen = 0;
    struct sockaddr_in client_address;
    addrlen = sizeof(client_address);
    return accept(socket,(struct sockaddr*) &client_address,&addrlen);
}

/**
* Using getsockname() to get the local bind ip
* @param sock Commander socket connection
* @param ip Result ip array (length must be 4 or greater)
* result IP array e.g. {127,0,0,1}
*/
void getip(int sock, int *ip)
{
    socklen_t addr_size = sizeof(struct sockaddr_in);
    struct sockaddr_in addr;
    getsockname(sock, (struct sockaddr *)&addr, &addr_size);
    int host,i;

    host = (addr.sin_addr.s_addr);
    for(i=0; i<4; i++){
        ip[i] = (host>>i*8)&0xff;
    }
}

/**
 * Parsing the getpeername() method to get the remote side connection ip
 * @param sock, pass in the socket and parse the information of it.
 * @param ip, result ip array numbers of remote connection side.
 */
void get_peer_ip(int sock, int *ip)
{
    socklen_t addr_size = sizeof(struct sockaddr_in);
    struct sockaddr_in addr;
    getpeername(sock,(struct sockaddr*)&addr, &addr_size);
    int host,i;

    host = (addr.sin_addr.s_addr);
    for( i=0;i<4;i++)
    {
        ip[i] = (host>>i*8)&0xff;
    }
}


/**
* Using getsockname() to get the local bind ip
* @param sock Commander socket connection
* @param ip_str Result ip ASCII string separated by dot '.'
*/
void getip_ascii(int sock, char* ip_str)
{
    int ip[4];
    getip(sock,ip);
    sprintf(ip_str,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]);
}

void get_peer_ip_ascii(int sock, char* ip_str)
{
    int ip[4];
    get_peer_ip(sock,ip);
    sprintf(ip_str,"%d.%d.%d.%d",ip[0],ip[1],ip[2],ip[3]);
}
/**
* Lookup enum value of string
* @param cmd Command string
* @return Enum index if command found otherwise -1
*/
int lookup_cmd(char *cmd){
    const int cmdlist_count = sizeof(cmdlist_str)/sizeof(char *);
    return lookup(cmd, cmdlist_str, cmdlist_count);
}

/**
* General lookup for string arrays
* It is suitable for smaller arrays, for bigger ones trie is better
* data structure for instance.
* @param needle String to lookup
* @param haystack String array, it contains many char*
* @param count Size of haystack
*/
int lookup(char *needle, const char **haystack, int count)
{
    int i;
    for(i=0; i<count; i++){
        if(strcmp(needle,haystack[i])==0)return i;
    }
    return -1;
}


/** 
* Writes current state to client
*/
void write_state(State *state)
{
    write(state->connection, state->message, strlen(state->message));
}

/**
* Generate random port for passive mode
* @param state Client state
*/
void gen_port(Port *port)
{
    srand(time(NULL));
    port->p1 = 128 + (rand() % 64);
    port->p2 = rand() % 0xff;

}

//p: format - port1,port2
void gen_port2(char *p, Port *port)
{
    char* f = strchr(p, ',');
    *f = 0;
    port->p1 = atoi(p);
    port->p2 = atoi(f + 1);

}

/**
* Parses FTP command string into struct
* @param cmdstring Command string (from ftp client)
* @param cmd Command struct
*/
void parse_command(char *cmdstring, Command *cmd)
{
    sscanf(cmdstring,"%s %s",cmd->command,cmd->arg);
}

/**
* Handles zombies
* @param signum Signal number
*/
void my_wait(int signum)
{
    int status;
    wait(&status);
}

/**
 * Free the malloced address of state and the state itself
 * @param state
 */
void free_state(struct State *state){
    free(state->userIP);
    free(state->oldName);
    free(state->username);
    free(state);
}
